﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

using Matchingo.Common.Constants;
using Matchingo.Common.Types.Enumerations;

namespace Matchingo.Modules.ParticleRenderModule.Views
{
    public partial class ParticleView : UserControl, IParticleEffectsView
    {
        #region ========== Private Constants ==========

        // NOTE:: for some reason, the first time this sound plays it is twice as loud as the rest of the time, i believe it may
        // be relative to Silverlight issues playing short sounds. for now we will set the volume to 40% for the frist time, and
        // 100% for the following times so that the volume is consistent
        private const double FIRST_MATCH_VOLUME_FACTOR = 0.40D;

        private const int OUT_OF_BOUND_MARGIN = 20; // how far off the screen the particle goes before being removed
        private const double TIME_ELAPSED = 0.1;

        #endregion

        #region ========== Private Properties ==========

        private static readonly Random RandomGenerator = new Random();

        private ParticleEffectsViewModel Model { get; set; }
        private List<Particle> Particles { get; set; }

        private bool FirstMatchPopSound = true;
		
        #endregion

        #region ========== Constructor ==========

        public ParticleView()
        {
            InitializeComponent();            
        }

        #endregion

        #region ========== Public Methods ==========

        public void Initialize(ParticleEffectsViewModel model)
        {
            Model = model;
            DataContext = Model;

            Particles = new List<Particle>();

            CompositionTarget.Rendering += CompositionTargetRendering;            
        }

        public void FireParticles(double centerX, double centerY, ParticleTypeEnumeration.ParticleType particleType,
            int particleCount, int matchComboCount, bool withSound)
        {
			if (withSound)
			{
				switch (particleType)
				{
					case ParticleTypeEnumeration.ParticleType.FlipCelebration:
						PlayFlippingNoise();
						break;
					case ParticleTypeEnumeration.ParticleType.MatchCelebration:
						PlayMatchingNoise();
						break;
				}
			}

            // create XX random particles           
            for (int i = 0; i < particleCount; i++)
            {
                Particle p = CreateParticle(centerX, centerY, particleType, matchComboCount);
                Particles.Add(p);
            }
        }

        #endregion

        #region ========== Private Methods ==========

        private Particle CreateParticle(double centerX, double centerY, ParticleTypeEnumeration.ParticleType particleType, int matchComboCount)
        {
            Particle p = new Particle();
            p.X = centerX;
            p.Y = centerY;

            // get a random velocity (one axis can be as low as 0, but not both, so we randomize which one we allow to go to 0)..
            if (RandomGenerator.Next(2) == 1)
            {
                p.VelocityX = RandomGenerator.Next(0, CommonConstants.PARTICLE_COUNT_VELOCITY_MAX);
                p.VelocityY = RandomGenerator.Next(CommonConstants.PARTICLE_COUNT_VELOCITY_MIN, CommonConstants.PARTICLE_COUNT_VELOCITY_MAX);
            }
            else
            {
                p.VelocityX = RandomGenerator.Next(CommonConstants.PARTICLE_COUNT_VELOCITY_MIN, CommonConstants.PARTICLE_COUNT_VELOCITY_MAX);
                p.VelocityY = RandomGenerator.Next(0, CommonConstants.PARTICLE_COUNT_VELOCITY_MAX);
            }

            // .. and direction
            if (RandomGenerator.Next(2) == 1)
            {
                p.VelocityX = p.VelocityX * -1;
            }
            if (RandomGenerator.Next(2) == 1)
            {
                p.VelocityY = p.VelocityY * -1;
            }

            // get a particle instance and add it to the UI canvas
            p.UIItem = Model.GetParticleInstance(particleType, matchComboCount);
            ParticleCanvas.Children.Add(p.UIItem);

            return p;
        }

        private void UpdateParticles()
        {
            if (Particles == null || Particles.Count == 0)
                return;

            foreach (Particle p in Particles)
            {
                // update the particle's X, Y position
                p.X = p.X + (p.VelocityX * TIME_ELAPSED);
                p.Y = p.Y + (p.VelocityY * TIME_ELAPSED);

                TranslateTransform trans = p.UIItem.RenderTransform as TranslateTransform;
                p.UIItem.RenderTransform = trans;
                trans.X = p.X;
                trans.Y = p.Y;
            }

            // remove dead particles
            RemoveDeadParticles();
        }

        private void RemoveDeadParticles()
        {
            if (Particles == null || Particles.Count == 0)
                return;

            // we dont want any particles that are outside the borders of the window, so use LINQ to set the
            // particle collection to a subset of itself (the set which is still in bound)
            List<Particle> deadParticles = (from p in Particles
                                            where p.X < -OUT_OF_BOUND_MARGIN ||
                                                  p.X > CommonConstants.GAME_WINDOW_WIDTH + OUT_OF_BOUND_MARGIN ||
                                                  p.Y < -OUT_OF_BOUND_MARGIN ||
                                                  p.Y > CommonConstants.GAME_WINDOW_HEIGHT + OUT_OF_BOUND_MARGIN
                                            select p).ToList();

            DestroyParticles(deadParticles);

            // if we are outside the max then forcefully clean up
            TrimParticleSetToCount(CommonConstants.PARTICLE_COUNT_BEFORE_CLEANUP);
        }

        private void TrimParticleSetToCount(int allowedCount)
        {
            // if we dont have any particles, or we are within the allowed count then return
            if (Particles == null || Particles.Count == 0 || Particles.Count <= allowedCount)
                return;

            // get the first XX particles where XX is the number over the allowed particle count
            List<Particle> deadParticles = Particles.GetRange(0, Particles.Count - allowedCount);
            DestroyParticles(deadParticles);
        }

        private void DestroyParticles(IEnumerable<Particle> particles)
        {
            foreach (Particle p in particles)
            {
                ParticleCanvas.Children.Remove(p.UIItem);
                Particles.Remove(p);
            }
        }

		private void PlayFlippingNoise()
		{

			int noise = RandomGenerator.Next(1, 4);
			switch (noise)
			{
				case 1:
					flip_1_wma.Position = TimeSpan.Zero;
					flip_1_wma.Volume = Model.SoundVolume;
					flip_1_wma.Play();
					break;
				case 2:
					flip_2_wma.Position = TimeSpan.Zero;
					flip_2_wma.Volume = Model.SoundVolume;
					flip_2_wma.Play();
					break;
				case 3:
					flip_3_wma.Position = TimeSpan.Zero;
					flip_3_wma.Volume = Model.SoundVolume;
					flip_3_wma.Play();
					break;
				default:
                    throw new ArgumentException("ParticleView.PlayFlippingNoise() - cannot find requested noise");
			}
		}

		private void PlayMatchingNoise()
		{
			if (FirstMatchPopSound)
			{
				boxPop3_wma.Volume = FIRST_MATCH_VOLUME_FACTOR * Model.SoundVolume;
				FirstMatchPopSound = false;
			}
			else
			{
				boxPop3_wma.Volume = Model.SoundVolume;
			}

			boxPop3_wma.Position = TimeSpan.Zero;
			boxPop3_wma.Play();
		}

        #endregion

        #region ========== Event Handlers ==========

        private void CompositionTargetRendering(object sender, EventArgs e)
        {
            UpdateParticles();
        }

        #endregion
    }

    public class Particle
    {
        public double X { get; set; }
        public double Y { get; set; }
        public double VelocityX { get; set; }
        public double VelocityY { get; set; }

        public UIElement UIItem { get; set; }
    }
}